package com.allesblinkt.mucom.client;

import java.util.Vector;

import com.allesblinkt.mucom.common.Constants;

import processing.core.PApplet;
import processing.core.PImage;
import traer.physics.Particle;
import traer.physics.ParticleSystem;
import mathematik.Vector2f;

public class Organism implements Constants {

	private static final int SEGMENTS = 30;
	private static final float INIT_RADIUS = 100;
	private static final float MASS = 10f;
	private static final float DRAG = 0.1f;

	private static final float TURN_RANGE = 50f;
	private static final float TURN_ANGLE = (float) (Math.PI / 1.5f);

	/**
	 * Rest length of the spring connecting the moverParticle to the center of the amoeba
	 */
	private static final float MOVER_DISTANCE = TURN_RANGE / 4f;

	private float progress = 0;

	/**
	 * Main texture mapped on the amoeba's geometry
	 */
	private PImage texture;

	private Vector2f centerPoint;
	private Particle centerParticle;

	private Vector2f moverPoint;
	private Particle moverParticle;

	private Vector<Vector2f> waypoints;

	private final ClientApp app;

	private Vector2f[] skinPoints = new Vector2f[SEGMENTS];
	private Particle[] skinParticles = new Particle[SEGMENTS];

	private ParticleSystem _mySystem;
	private float progressAdvance;
	
	
	public int index;

	public Organism(ClientApp theApp) {
		app = theApp;

		texture = app.loadImage("client/lsd_copy.png");

		waypoints = new Vector<Vector2f>();
		generatePath();

		Vector2f theStart = new Vector2f(waypoints.get(0));

		_mySystem = new ParticleSystem(0.0f, DRAG);

		centerPoint = new Vector2f(theStart.x - MOVER_DISTANCE, theStart.y);
		centerParticle = _mySystem.makeParticle(MASS * 10, centerPoint.x,
				centerPoint.y, 0);

		moverPoint = new Vector2f(theStart.x, theStart.y);
		moverParticle = _mySystem.makeParticle(MASS * 100, moverPoint.x,
				moverPoint.y, 0);
		moverParticle.makeFixed();

		_mySystem.makeSpring(moverParticle, centerParticle, 1f, DRAG,
				MOVER_DISTANCE);

		/* Create Skin */

		float alpha = (float) PApplet.TWO_PI / (float) SEGMENTS;
		float segmentLength = PApplet.sin(alpha / 2f) * INIT_RADIUS * 2f;

		for (int i = 0; i < SEGMENTS; i++) {

			float rad = i * alpha;
			skinParticles[i] = _mySystem.makeParticle(MASS, theStart.x
					+ INIT_RADIUS * PApplet.sin(rad), theStart.y + INIT_RADIUS
					* PApplet.cos(rad), 0);
			skinPoints[i] = new Vector2f(theStart.x + INIT_RADIUS
					* PApplet.sin(rad), theStart.y + INIT_RADIUS
					* PApplet.cos(rad));
		}

		/* Create Skin Springs */
		for (int i = 0; i < SEGMENTS; i++) {

			_mySystem.makeSpring(skinParticles[i], skinParticles[(i + 1)
					% SEGMENTS], 2f, 0.2f, segmentLength);
		}

		/* Create inner springs */
		for (int i = 0; i < SEGMENTS; i += 2) {

			_mySystem.makeSpring(skinParticles[i], centerParticle, 1f, 0.2f,
					INIT_RADIUS);
		}

		progressAdvance = 1f / (SCREENTIME * FRAMERATE);

	}

	public void continueTravel(){
		
		progress += progressAdvance;

	}
	
	public void animate() {

		if (progress > 1)
			progress = 1.0f;

		Vector2f thePosition = getTargetPoint(progress);

		moveTo(thePosition.x, thePosition.y);

		/* Dirty hack 1 2 3 */
		for (int i = 0; i < SEGMENTS; i++) {
			final Particle p = skinParticles[i];
			p.moveTo(p.position().x(), p.position().y(), 0);
		}

		_mySystem.advanceTime(2f);

		/* Reset vector positions. This is costly. Remove sometime. */
		centerPoint.set(centerParticle.position().x(), centerParticle
				.position().y());

		moverPoint.set(moverParticle.position().x(), moverParticle.position()
				.y());

		for (int i = 0; i < SEGMENTS; i++) {
			skinPoints[i].set(skinParticles[i].position().x(), skinParticles[i]
					.position().y());
		}

		for (int i = 0; i < SEGMENTS; i++) {
			final Particle p = skinParticles[i];
			p.moveTo(p.position().x(), p.position().y(), 0);
		}

	}

	private float getU(int segment) {
		float rad = segment * (float) PApplet.TWO_PI / (float) SEGMENTS;

		return (PApplet.cos(rad) + 1f) / 2f;
	}

	private float getV(int segment) {
		float rad = segment * (float) PApplet.TWO_PI / (float) SEGMENTS;

		return (PApplet.sin(rad) + 1f) / 2f;
	}

	public Vector2f position() {
		return centerPoint;
	}
	
	public Vector2f nextPosition() {
		return getTargetPoint(progress);
	}

	/**
	 * Generates the path the amoeba should travel on
	 */
	private void generatePath() {

		float availWidth = app.screen.width;
		float availHeight = app.screen.height;

		Vector2f start = new Vector2f(0 - INIT_RADIUS * 2f, app
				.random(availHeight*0.75f));
		Vector2f target = new Vector2f(availWidth + INIT_RADIUS * 2f, app
				.random(availHeight*0.75f));

		waypoints.add(start);

		boolean isSearching = true;

		while (isSearching) {
			Vector2f prev = (Vector2f) waypoints.get(waypoints.size() - 1);

			Vector2f toTarget = new Vector2f(target);
			toTarget.sub(prev);

			Vector2f next = new Vector2f(Float.MAX_VALUE, Float.MAX_VALUE);

			while (!(next.y > 0 && next.y < availHeight)) {
				float direction = toTarget.direction()
						+ app.random(-TURN_ANGLE, TURN_ANGLE);

				next.set(1, 0);
				next.rotate(direction);
				next.scale(TURN_RANGE);

				next.add(prev);

			}

			waypoints.add(next);

			if (next.distance(target) <= TURN_RANGE * 2)
				isSearching = false;

		}

		
		target.x = target.x + INIT_RADIUS;
		
		waypoints.add(target);

	}

	private Vector2f getTargetPoint(float theProgress) {
		 index = PApplet.min((int) ((waypoints.size() - 1) * theProgress),
				waypoints.size() - 2);
		float blend = (progress * (waypoints.size() - 1)) - index;

		Vector2f result = new Vector2f(waypoints.get(index));

		Vector2f interim = new Vector2f(waypoints.get(index + 1));
		interim.sub(result);
		interim.scale(blend);

		result.add(interim);

		return result;
	}
	
	

	@SuppressWarnings("deprecation")
	public void draw() {

		app.pushMatrix();
		app.noStroke();
		app.fill(0);

		for (int i = 0; i < SEGMENTS; i++) {
			app.beginShape();
			app.textureMode(PApplet.NORMALIZED);

			app.texture(texture);

			app.vertex(centerPoint.x, centerPoint.y, 0.5f, 0.5f);
			app.vertex(skinPoints[i].x, skinPoints[i].y, getU(i), getV(i));
			app.vertex(skinPoints[(i + 1) % SEGMENTS].x, skinPoints[(i + 1)
					% SEGMENTS].y, getU((i + 1) % SEGMENTS), getV((i + 1)
					% SEGMENTS));

			app.endShape(PApplet.CLOSE);
		}

		app.stroke(0, 0);

		app.stroke(0);

		app.popMatrix();

	}

	
	/**
	 * @param theX
	 * @param theY
	 */
	private void moveTo(float theX, float theY) {
		moverParticle.moveTo(theX, theY, 0);
	}

	/**
	 * @return the Progress ranging from 0 to 1
	 */
	public float getProgress() {
		return progress;
	}

	public Vector<Vector2f> waypoints() {
		return waypoints;
	}

}
